/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "DXDrawPrimitive.h"
#include "IRenderer.h"

namespace directx {

DXDrawPrimitive::DXDrawPrimitive(void)
:	m_pMesh(NULL),
	m_pVertexDecl(NULL),
	m_pFullBuffer(NULL)
{
	D3DVERTEXELEMENT9 elements[] = 
	{
		{0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
		{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
		{0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
		D3DDECL_END()
	};	
	pd3dDevice->CreateVertexDeclaration(elements, &m_pVertexDecl);
}

DXDrawPrimitive::~DXDrawPrimitive(void)
{
	RELEASE( m_pMesh );
	RELEASE( m_pVertexDecl );
	RELEASE( m_pFullBuffer);
}

struct _VERTEX
{
    D3DXVECTOR3 pos;     // vertex position
    D3DXVECTOR3 norm;    // vertex normal
    float tu;            // texture coordinates
    float tv;
};

#define FVF_VERTEX    D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1

void DXDrawPrimitive::createSphere(float fRad, unsigned int slices, unsigned int stacks)
{
    LPD3DXMESH mesh;
	_VERTEX *pVerts = NULL;

    // create the sphere
	if (FAILED( D3DXCreateSphere(pd3dDevice, fRad, slices, stacks, &mesh, NULL) ))
        return;

    // create a copy of the mesh with texture coordinates,
    // since the D3DX function doesn't include them
	if (FAILED(mesh->CloneMeshFVF(D3DXMESH_SYSTEMMEM, FVF_VERTEX, pd3dDevice, &m_pMesh))) {
        // failed, return un-textured mesh
        m_pMesh = mesh;
		return;
	}

    // finished with the original mesh, release it
    mesh->Release();

    // lock the vertex buffer
    if (SUCCEEDED(m_pMesh->LockVertexBuffer(0, (LPVOID*) &pVerts))) {

        // get vertex count
        int numVerts=m_pMesh->GetNumVertices();

        // loop through the vertices
        for (int i=0; i<numVerts; ++i) {

            // calculate texture coordinates
            pVerts->tu=asinf(pVerts->norm.x)/D3DX_PI+0.5f;
            pVerts->tv=asinf(pVerts->norm.y)/D3DX_PI+0.5f;
            
            // go to next vertex
            ++pVerts;
        }

        // unlock the vertex buffer
        m_pMesh->UnlockVertexBuffer();
    }
}

void DXDrawPrimitive::createBox(float width, float height, float depth)
{
	// DirectX has a createbox function. Use this. 
	D3DXCreateBox(pd3dDevice, width, height, depth, &m_pMesh, NULL);
}

#if defined(DEBUG) | defined(_DEBUG)
	#ifndef HR
	#define HR(x)                                      \
	{                                                  \
		HRESULT hr = x;                                \
		if(FAILED(hr))                                 \
		{                                              \
			/*DXTrace(__FILE__, __LINE__, hr, #x, TRUE); */ \
		}                                              \
	}
	#endif

#else
	#ifndef HR
	#define HR(x) x;
	#endif
#endif 

#define EPSILON 0.001f

void DXDrawPrimitive::createPlane(const math::Vector3 &normal, float d, const math::Vector2i &tesselation)
{
	int mWidth = tesselation.x+1;
	int mDepth = tesselation.y+1;

	DWORD numTris  = tesselation.x*tesselation.y*2;
	DWORD numVerts = mWidth*mDepth;

	//===============================================================
	// Allocate the mesh.

	D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
	UINT numElems = 0;

	if (FAILED( m_pVertexDecl->GetDeclaration(elems, &numElems) )) 
	{
		coreGetLog()->WriteError("Could not get Vertex declaration", __FILE__,  __LINE__);
		return;
	}

	if (FAILED( D3DXCreateMesh(numTris, numVerts, D3DXMESH_MANAGED, elems, pd3dDevice, &m_pMesh)) ) 
	{
		coreGetLog()->WriteError("Could not create mesh for plane", __FILE__,  __LINE__);
		return;
	}

	//===============================================================
	// Write the grid vertices and triangles to the mesh.	

	VertexPNT* v = 0;
	HR(m_pMesh->LockVertexBuffer(0, (void**)&v));
	
	std::vector<math::Vector3> verts;
	std::vector<DWORD> indices;
	GenTriGrid(mWidth, mDepth, 1, 1, verts, indices);

	math::Vector3 n = normal;
	DWORD index = 0;

	for(int i = 0; i < mDepth; ++i) {
		for(int j = 0; j < mWidth; ++j) {
			index = i * mWidth + j;

			v[index].position  = verts[index] + math::Vector3(0, d, 0);
			// TODO Rotate the positions
			v[index].normal = n;
			v[index].texcoord = math::Vector2f((float)j/tesselation.x, (float)i/tesselation.y);
		}
	}
	HR(m_pMesh->UnlockVertexBuffer());

	//===============================================================
	// Write triangle data so we can compute normals.

	WORD* indexBuffPtr = 0;
	DWORD* attBuff = 0;
	HR(m_pMesh->LockIndexBuffer(0, (void**)&indexBuffPtr));
	HR(m_pMesh->LockAttributeBuffer(0, &attBuff));
	
	for (UINT i = 0; i < m_pMesh->GetNumFaces(); ++i) {
		indexBuffPtr[i*3+0] = (WORD)indices[i*3+0];
		indexBuffPtr[i*3+1] = (WORD)indices[i*3+1];
		indexBuffPtr[i*3+2] = (WORD)indices[i*3+2];

		attBuff[i] = 0; // All in subset 0.
	}

	HR(m_pMesh->UnlockAttributeBuffer());
	HR(m_pMesh->UnlockIndexBuffer());

	//===============================================================
	// Optimize for the vertex cache and build attribute table.

	DWORD* adj = new DWORD[m_pMesh->GetNumFaces()*3];
	HR(m_pMesh->GenerateAdjacency(EPSILON, adj));
	HR(m_pMesh->OptimizeInplace(D3DXMESHOPT_VERTEXCACHE|D3DXMESHOPT_ATTRSORT, adj, 0, 0, 0));

	delete[] adj;
}

	                                                               // # of texcoords 
#define D3DFVF_SCREENVERTEX ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 )
struct ScreenVertex
{
	float x, y, z, rhw;
	DWORD color;
	float tu, tv;
};

void DXDrawPrimitive::createScreenQuad(int width, int height, int offsetX, int offsetY, float scaleFactor, const math::Vector4 &color)
{
	void *pVertices = NULL;

	IRenderer* pRenderer = coreGetRenderer();

	if (width == -1) 
		width = pRenderer->getBackBufferSize().x;
	if (height == -1) 
		height = pRenderer->getBackBufferSize().y;

	ScreenVertex fullVertices[] = 
	{
		{static_cast<float>(offsetX)-.5f,					static_cast<float>(offsetY)-.5f,					0.0f,  1.0f, D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 0.0f,   0.01f },
		{static_cast<float>(offsetX+width*scaleFactor)-.5f,	static_cast<float>(offsetY)-.5f,					0.0f,  1.0f, D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 1.0f,   0.01f },
		{static_cast<float>(offsetX)-.5f,					static_cast<float>(offsetY+height*scaleFactor)-.5f,	0.0f,  1.0f, D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 0.0f,   0.99f },
		{static_cast<float>(offsetX+width*scaleFactor)-.5f,	static_cast<float>(offsetY+height*scaleFactor)-.5f,	0.0f,  1.0f, D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 1.0f,   0.99f },
	};

	if (SUCCEEDED( pd3dDevice->CreateVertexBuffer( 4 * sizeof( ScreenVertex ),
									D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 
									D3DFVF_SCREENVERTEX,
                                    D3DPOOL_DEFAULT, 
									&m_pFullBuffer, NULL ) )) 
	{

		m_pFullBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
		memcpy( pVertices, fullVertices, sizeof(fullVertices) );
		m_pFullBuffer->Unlock();
	}
}

#define D3DFVF_SIMPLEVERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 )
struct SimpleVertex
{
	float x, y, z;
	DWORD color;
	float tu, tv;
};
void DXDrawPrimitive::createQuad(int width, int height, const math::Vector4 &color)
{
	void *pVertices = NULL;

	IRenderer* pRenderer = coreGetRenderer();

	if (width == -1) 
		width = pRenderer->getBackBufferSize().x;
	if (height == -1) 
		height = pRenderer->getBackBufferSize().y;

	SimpleVertex fullVertices[] = 
	{
		{0,		0,		0.0f,  D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 0.0f,   0.0f },
		{(float)width,	0,	0.0f,  D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 1.0f,   0.0f },
		{0,	(float)height,	0.0f,  D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 0.0f,   1.0f },
		{(float)width, (float)height,	0.0f,  D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 1.0f,   1.0f },
	};

	if (SUCCEEDED( pd3dDevice->CreateVertexBuffer( 4 * sizeof( SimpleVertex ),
									D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 
									D3DFVF_SIMPLEVERTEX,
                                    D3DPOOL_DEFAULT, 
									&m_pFullBuffer, NULL ) )) 
	{
		m_pFullBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
		memcpy( pVertices, fullVertices, sizeof(fullVertices) );
		m_pFullBuffer->Unlock();
	}
}

void DXDrawPrimitive::draw()
{
	if (type == TYPE_SCREENQUAD) {
		// Render the quad on screen
		pd3dDevice->SetStreamSource( 0, m_pFullBuffer, 0, sizeof( ScreenVertex ) );
		pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
		pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );
	}
	else if (type == TYPE_QUAD) {
		// Render the quad on screen
		pd3dDevice->SetStreamSource( 0, m_pFullBuffer, 0, sizeof( SimpleVertex ) );
		pd3dDevice->SetFVF( D3DFVF_SIMPLEVERTEX );
		pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );
	}
	else {
		m_pMesh->DrawSubset(0);
	}
}

} // namepsace
